-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: middleware to short-circuit on 304 #15586
Conversation
Run & review this pull request in StackBlitz Codeflow. |
// For direct CSS requests, if the same CSS file is imported in a module, | ||
// the browser sends the request for the direct CSS request with the etag | ||
// from the imported CSS module. We ignore the etag in this case. | ||
const mixedEtag = | ||
!req.headers.accept?.includes('text/css') && | ||
isDirectRequest(moduleByEtag.url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to understand this part. Do you mean that the browser would send the same etag for text/css
requests and *.css
imports? That sounds strange. Is it a bug that also happens before?
I think it would still be nice to make the request work instead of bailing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this guard, this test was failing. The issue is that in that test we are both adding global.css
as a link in the html and then importing it. I tried to find a reason why we do so and I couldn't. But I think we should leave it as is because it actually helped to spot this bug.
I think our current system for .css
isn't ideal. What we do is adding a ?direct
flag when we detect that global.css
was requested as a link. But that ?direct
flag is internal, for the browser, it sees two global.css
requests with the same URL. We return different etags for each. When the browser requests the imported global.css
module the second time, it uses the etag of the linked one. I thought it could be a bug in chrome but I tested in Firefox and Safari and in all the browsers is the same.
This playground was still working because we did a getModuleByUrl before this PR, and it was done after adding ?direct
when it was a link. If the etag was mixed for imported css modules, then it will not match with the etag of the module and a regular 200 will be issued instead of a 304. So this PR is doing the same for this case, I don't know how we could avoid bailing out here. I think it is ok as the setup is an edge case.
An alternative would be to switch from ?direct
for linked CSS to using ?import
for imported CSS modules instead like we do with other assets (rewriting during import analysis). I think this would actually be cleaner. If we do so, then a bare .css request will always correspond to a link request, and a .css?import
will always be a module and these URLs are seen by the browser (and not like ?direct
that we add internally) so the requests are for different URLs. But I don't know if we can do this change given that it may have a big effect on the ecosystem, and it is hard to justify it when it is only an issue if the same .css is linked and imported at the same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah thanks. Sounds like a weird browser bug, but maybe for good reasons. Yeah in that case if it's rare, then I think we can simply bail then. Maybe we could add a debug message to detect this kind of requests if it later turns out to be more common than expected.
The ?import
idea does make more sense to me, but yeah maybe we can leave that until we need to make a bigger change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @sapphi-red, this may be interesting to you as I think you mentioned we could remove ?import
at one point, no? If we do so, we may hit this same etag issues for that assets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess Vary header (Vary: Accept
) is what we should use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh... this looks great, thanks @sapphi-red! I tried it out and it seems it fixes safari but I still see the same issue in Chrome and Firefox. So maybe there are browsers bugs here? I think we should keep the guard for now and aim to use Vary header in the future (for when we'll try to remove ?import
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems I misunderstood the Vary header. Vary header doesn't affect conditional requests.
Also browsers seems to only use a single E-Tag in If-None-Match
header. I guess there isn't a way to tell browsers to send different If-None-Match
header for a same URL and a different header.
// check if we can return 304 early | ||
const ifNoneMatch = req.headers['if-none-match'] | ||
if (ifNoneMatch) { | ||
const moduleByEtag = server.moduleGraph.getModuleByEtag(ifNoneMatch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a file is added in public directory, I think we need to delete a entry from server.moduleGraph.etagToModuleMap
.
For example, if /src/foo.ts
existed and later on /public/src/foo.ts
is added, then /public/src/foo.ts
should be returned instead of the transpileed /src/foo.ts
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
More context for others. We are serving public files using sirv, and I see that the way it computes its etag is:
if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`;
There shouldn't be any overlap between public etags and processed etags.
But as this two URLs are the same, the browser may request the public file using the etag of processed module because of the same issue discussed here.
But if the source file existed but was removed, the etag is no longer going to be there. The only case where this is an issue is when there are both a public and a source file at the same time (and the public file was added later). If we find the module by id and remove its etag, then the browser may still request the url with the wrong etag but it should still pick up the public file.
I think it should be fixed by e9a6011
/ecosystem-ci run |
📝 Ran ecosystem CI on
|
I saw the same Nuxt fail in other PRs when running ecosystem-ci, I think it isn't related to this PR |
I noticed that this breaks |
The names in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍 I'm not sure how confident are you with the new module graph API, and whether we should mark them as internal if we ever need to change it, but otherwise we can give this a shot!
I'm not sure if we should mark it as internal yet, because if not @sapphi-red won't be able to fix the edge case he talked about here 🤔 Let's merge it without marking as internal, but we can revisit this before releasing Vite 5.1. |
Description
I'm checking the work needed in the server to answer on 304 once it is warmed up. We return 304 when:
There isn't much work we do to reach the current condition, but we still do a few things.
getModuleByUrl
that also adds up.This PR implements a fast path for 304, with a new middleware after CORS that kicks in when there is an etag, and uses a etagToModuleMap.
I'm seeing a ~3-4% improvement when forcing a reload on my M1. I would say the extra map is ok here. It would be interesting to see the effect on slower machines too.
What is the purpose of this pull request?